#include "modelSprite.h"
#include "components/3D/sweety_3D_Camera.h"
#include "framework/sweety_Render.h"
#include "third-party/glfw/include/glfw3.h"
#include "components/3D/sweety_3D_Model.h"

#define SHADER_PATH "Shaders/"
#define MODEL_PATH "Resourses/Models/"

namespace SW
{
    ModelSprite::ModelSprite()
    {
        float vtx[] = {
           -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
            0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
            0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
            0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
           -0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,
           -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,

           -0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,
            0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,
            0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,
            0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,
           -0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,
           -0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,

           -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,
           -0.5f,  0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
           -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
           -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,
           -0.5f, -0.5f,  0.5f, -1.0f,  0.0f,  0.0f,
           -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,

            0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,
            0.5f,  0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
            0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
            0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,
            0.5f, -0.5f,  0.5f,  1.0f,  0.0f,  0.0f,
            0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,

           -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,
            0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,
            0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
            0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
           -0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,
           -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,

           -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,
            0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,
            0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
            0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
           -0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,
           -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f
        };
       
        if (nullptr == m_LightVaoPtr)
        {
            m_LightVaoPtr = new VaoClass(vtx, sizeof(vtx), 6);
        }

        InitShaders();
        InitLampShaders();

        LoadModel();
    }

    ModelSprite::~ModelSprite()
    {
        if (m_ModelPtr)
        {
            delete m_ModelPtr;
            m_ModelPtr = nullptr;
        }

        if (m_LightModelPtr)
        {
            delete m_LightModelPtr;
            m_LightModelPtr = nullptr;
        }
    }

    void ModelSprite::InitShaders()
    {
        m_Shader.useVertex(); m_Shader.useFragment();

        m_Shader.getVertex()->Load("", 1, SHADER_PATH "modelSpriteVert.glsl");

        m_Shader.getFragment()->Load("", 1, SHADER_PATH "modelSpriteFrag.glsl");

        // The vertex and fragment are added to the program object
        m_Shader.addVertex();
        m_Shader.addFragment();

        m_Shader.link();

        m_Shader.bindPipeline();
        m_Shader.useProgram();

        m_Shader.reset();
    }

    void ModelSprite::InitLampShaders()
    {
        m_LampShader.useVertex(); m_LampShader.useFragment();

        m_LampShader.getVertex()->Load("", 1, SHADER_PATH "lampSpriteVert.glsl");

        m_LampShader.getFragment()->Load("", 1, SHADER_PATH "lampSpriteFrag.glsl");

        // The vertex and fragment are added to the program object
        m_LampShader.addVertex();
        m_LampShader.addFragment();

        m_LampShader.link();

        m_LampShader.bindPipeline();
        m_LampShader.useProgram();

        m_LampShader.reset();
    }

    void ModelSprite::LoadModel()
    {
        if (!m_ModelPtr)
        {
            m_ModelPtr = new Model(MODEL_PATH "backpack/backpack.obj");
        }

        if (!m_LightModelPtr)
        {
            m_LightModelPtr = new Model(MODEL_PATH "nanosuit/Bulb.obj");
        }
    }

    void ModelSprite::Render(const mat4& modelTrans)
    {     
        Camera3D* camera = dynamic_cast<Camera3D*>(Renderer::GetCurrentCamera());
        if (!camera)
        {
            return;
        }

        if (!m_IsResetCampos)
        {
            camera->SetPos(vec3(0.f, 0.f, 4.f));
            m_IsResetCampos = true;
        }         

        for (int i = 0; i < 2; i++)
        {
            m_PointLightPositions[i].x = sin(glfwGetTime()) * 2.f + 1.0f;
            m_PointLightPositions[i].y = sin(glfwGetTime() / 2.f) * 1.f; 
            //m_PointLightPositions[i].z = sin(glfwGetTime() / 2.f) * 1.f;
        }

        // Create camera transformations
        mat4 model(1.0f), view(1.0f), projection(1.0f);
        view = camera->GetViewTrans();
        projection = camera->GetProjectionTrans();

        {
            m_Shader.bindPipeline();
            m_Shader.useProgram();

            GLuint Model_SL = m_Shader.getUniformLocation("model");
            GLuint View_SL = m_Shader.getUniformLocation("view");
            GLuint Projection_SL = m_Shader.getUniformLocation("projection");
            GLint viewPosLoc = m_Shader.getUniformLocation("viewPos");
           
            glUniformMatrix4fv(View_SL, 1, GL_FALSE, value_ptr(view));
            glUniformMatrix4fv(Projection_SL, 1, GL_FALSE, value_ptr(projection));
            vec3 camPos = camera->GetPosition();
            glUniform3fv(viewPosLoc, 1, value_ptr(camPos));

            // Point light 1
            glUniform3fv(m_Shader.getUniformLocation("pointLights[0].position"), 1, value_ptr(m_PointLightPositions[0]));
            glUniform3fv(m_Shader.getUniformLocation("pointLights[0].ambient"), 1, value_ptr(vec3(0.05f, 0.05f, 0.05f)));
            glUniform3fv(m_Shader.getUniformLocation("pointLights[0].diffuse"), 1, value_ptr(vec3(1.0f, 1.0f, 1.0f)));
            glUniform3fv(m_Shader.getUniformLocation("pointLights[0].specular"), 1, value_ptr(vec3(1.0f, 1.0f, 1.0f)));
            glUniform1f(m_Shader.getUniformLocation("pointLights[0].constant"), 1.0f);
            glUniform1f(m_Shader.getUniformLocation("pointLights[0].linear"), 0.009);
            glUniform1f(m_Shader.getUniformLocation("pointLights[0].quadratic"), 0.0032);
            // Point light 2   
            glUniform3fv(m_Shader.getUniformLocation("pointLights[0].position"), 1, value_ptr(m_PointLightPositions[1]));
            glUniform3fv(m_Shader.getUniformLocation("pointLights[1].ambient"), 1, value_ptr(vec3(0.05f, 0.05f, 0.05f)));
            glUniform3fv(m_Shader.getUniformLocation("pointLights[1].diffuse"), 1, value_ptr(vec3(1.0f, 1.0f, 1.0f)));
            glUniform3fv(m_Shader.getUniformLocation("pointLights[1].specular"), 1, value_ptr(vec3(1.0f, 1.0f, 1.0f)));
            glUniform1f(m_Shader.getUniformLocation("pointLights[1].constant"), 1.0f);
            glUniform1f(m_Shader.getUniformLocation("pointLights[1].linear"), 0.009);
            glUniform1f(m_Shader.getUniformLocation("pointLights[1].quadratic"), 0.0032);

            //model = translate(model, vec3(0.0f, 1.75f, 0.0f)); // Translate it down a bit so it's at the center of the scene
            model = scale(model, vec3(0.7f, 0.7f, 0.7f));	// It's a bit too big for our scene, so scale it down        
            glUniformMatrix4fv(Model_SL, 1, GL_FALSE, value_ptr(model));

            m_ModelPtr->Draw(&m_Shader);
        }

        {   // light shader
            m_LampShader.bindPipeline();
            m_LampShader.useProgram();

            GLuint Model_SL = m_LampShader.getUniformLocation("model");
            GLuint View_SL = m_LampShader.getUniformLocation("view");
            GLuint Projection_SL = m_LampShader.getUniformLocation("projection");

            for (int i = 0; i < 2; i++)
            {
                mat4 model(1.0f), view(1.0f), projection(1.0f);
                model = translate(model, m_PointLightPositions[i]);
                model = scale(model, vec3(0.2f)); // Make it a smaller cube
                view = camera->GetViewTrans();
                projection = camera->GetProjectionTrans();
                glUniformMatrix4fv(Model_SL, 1, GL_FALSE, value_ptr(model));
                glUniformMatrix4fv(View_SL, 1, GL_FALSE, value_ptr(view));
                glUniformMatrix4fv(Projection_SL, 1, GL_FALSE, value_ptr(projection));
                m_LightVaoPtr->draw(false, false);
            }
            m_LampShader.reset();
        }
     
    }
}